use crate::config::app_error::AppResult;
use crate::extension::index::index::index_specs::{DefaultIndexSpecs, IndexSpecs};
use crate::extension::index::index::query::index_attribute::{
    IndexAttribute, IndexAttributeFactory,
};
use crate::extension::index::index::query::index_spec;
use crate::extension::index::index::query::index_spec::{IndexSpec, OrderType};
use crate::extension::index::scheme_manager;
use crate::extension::index::scheme_manager::Scheme;
use halo_model::{ExtensionGVK, ExtensionOperator, GVK};
use std::collections::HashMap;

pub struct IndexSpecRegistry {
    pub extension_index_specs: HashMap<String, DefaultIndexSpecs>,
}

impl IndexSpecRegistry {
    pub fn new() -> Self {
        IndexSpecRegistry {
            extension_index_specs: HashMap::new(),
        }
    }

    fn use_default_index_spec<T:ExtensionGVK>(index_specs: &mut DefaultIndexSpecs) -> AppResult<()> {
        let spec = index_spec::primary_key_index_spec::<T>();
        index_specs.add(spec)?;

        let creation_index_spec = IndexSpec::new(
            "metadata.creationTimestamp".to_string(),
            OrderType::ASC,
            false,
            IndexAttributeFactory::simple_attribute("".to_string(), |e: &T| {
                e.get_metadata()
                    .get_creation_timestamp()
                    .map(|v| v.to_string())
                    .unwrap_or("".to_string())
            }),
        );

        index_specs.add(creation_index_spec)?;

        let deletion_index_spec = IndexSpec::new(
            "metadata.deletionTimestamp".to_string(),
            OrderType::ASC,
            false,
            IndexAttributeFactory::simple_attribute("".to_string(), |e: &T| {
                e.get_metadata()
                    .get_deletion_timestamp()
                    .map(|v| v.to_string())
                    .unwrap_or("".to_string())
            }),
        );

        index_specs.add(deletion_index_spec)?;

        index_specs.add(index_spec::label_index_spec::<T>())?;
        Ok(())
    }

    pub(crate) fn index_for<T:ExtensionGVK>(&mut self, scheme: &Scheme) -> AppResult<()> {
        let key_space = self.get_key_space(scheme);

        let mut index_specs = DefaultIndexSpecs::new();

        Self::use_default_index_spec::<T>(&mut index_specs)?;

        self.extension_index_specs
            .insert(key_space.clone(), index_specs);

        // self.extension_index_specs.get(&key_space).unwrap();

        Ok(())
    }
    pub(crate) fn consumer<F>(&mut self, scheme: &Scheme, f: F) -> AppResult<()>
    where
        F: Fn(&mut DefaultIndexSpecs) -> AppResult<()>,
    {
        let key_space = self.get_key_space(scheme);

        let mut index_specs = self.extension_index_specs.get_mut(&key_space);
        if let Some(index_specs) = index_specs {
            f(index_specs)?
        }
        Ok(())
    }
    pub(crate) fn get_index_specs(&mut self, scheme: &Scheme) -> AppResult<DefaultIndexSpecs> {
        let key_space = self.get_key_space(scheme);

        self.extension_index_specs.remove(&key_space)
            .map(|index_specs| Ok(index_specs))
            .unwrap_or_else(|| {
                Err(anyhow::anyhow!("No index specs found for extension_index type: {:?}, make sure you have called indexFor() before calling getIndexSpecs()", scheme.group_version_kind.clone()).into())
            })
    }

    pub(crate) fn contains(&self, scheme: &Scheme) -> bool {
        let key_space = self.get_key_space(scheme);
        self.extension_index_specs.contains_key(&key_space)
    }

    pub(crate) fn remove_index_specs(&mut self, scheme: &Scheme) {
        let key_space = self.get_key_space(scheme);
        self.extension_index_specs.remove(&key_space);
    }

    pub(crate) fn get_key_space(&self, scheme: &Scheme) -> String {
        scheme_manager::build_store_name_prefix(scheme)
    }
}

#[cfg(test)]
mod tests {
    use crate::extension::index::index::index_spec_registry::IndexSpecRegistry;
    use crate::extension::index::index::index_specs::IndexSpecs;
    use crate::extension::index::scheme_manager;
    use crate::extension::index::scheme_manager::Scheme;
    use crate::get_scheme_manager;
    use crate::tests::index_view_data_set::FakeExtension;

    #[tokio::test]
    async fn index_for() {
        let mut index_spec_registry = IndexSpecRegistry::new();
        let mut scheme_manager = get_scheme_manager!().unwrap();
        scheme_manager.register::<FakeExtension>().await;
        let scheme = Scheme::new::<FakeExtension>().await;

        index_spec_registry.index_for::<FakeExtension>(&scheme).unwrap();

        assert_eq!(index_spec_registry.extension_index_specs.len(), 4);
    }

    #[tokio::test]
    async fn contains() {
        let mut index_spec_registry = IndexSpecRegistry::new();
        let scheme_manager = get_scheme_manager!().unwrap();
        scheme_manager.register::<FakeExtension>().await;
        let scheme = Scheme::new::<FakeExtension>().await;

        index_spec_registry.index_for::<FakeExtension>(&scheme).unwrap();

        let contains = index_spec_registry.contains(&scheme);

        assert!(contains);
    }
    #[tokio::test]
    async fn get_key_space() {
        let mut index_spec_registry = IndexSpecRegistry::new();

        let scheme_manager = get_scheme_manager!().unwrap();
        scheme_manager.register::<FakeExtension>().await;
        let scheme = Scheme::new::<FakeExtension>().await;

        let key_space = index_spec_registry.get_key_space(&scheme);

        assert_eq!(key_space, "/registry/test.halo.run/fakes");
    }

    #[tokio::test]
    async fn get_index_specs() {
        let mut index_spec_registry = IndexSpecRegistry::new();

        let scheme_manager = get_scheme_manager!().unwrap();
        scheme_manager.register::<FakeExtension>().await;
        let scheme = Scheme::new::<FakeExtension>().await;

        let index_specs = index_spec_registry.get_index_specs(&scheme);

        assert!(index_specs.is_err());
        match index_specs {
            Ok(_) => {}
            Err(e) => assert!(e
                .to_string()
                .contains("No index specs found for extension_index type: ")),
        }

        let _ = index_spec_registry.index_for::<FakeExtension>(&scheme).unwrap();

        let key_space = index_spec_registry.get_index_specs(&scheme);

        assert!(key_space.is_ok());
    }
}
